package com.example.redislearning.service.impl;

import com.example.redislearning.entity.Course;
import com.example.redislearning.entity.UserCourse;
import com.example.redislearning.mapper.CourseMapper;
import com.example.redislearning.mapper.UserCourseMapper;
import com.example.redislearning.service.LearningProgressService;
import com.example.redislearning.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 学习进度服务实现类
 * 基于Redis的学习进度跟踪和统计
 * 支持学习时长记录、课程完成状态、进度排行榜等功能
 */
@Service
public class LearningProgressServiceImpl implements LearningProgressService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private UserCourseMapper userCourseMapper;

    @Autowired
    private CourseMapper courseMapper;

    // 学习进度缓存时间：1小时
    private static final Integer PROGRESS_CACHE_HOURS = 1;

    // 学习时长排行榜有效期：7天
    private static final Integer STUDY_TIME_RANK_DAYS = 7;

    /**
     * 开始学习课程
     * @param userId 用户ID
     * @param courseId 课程ID
     * @return 是否成功开始学习的布尔值
     * @throws RuntimeException 当开始学习失败时抛出异常
     */
    @Override
    public boolean startLearning(Integer userId, Integer courseId) {
        try {
            String learningKey = RedisKeyUtil.getUserLearningKey(userId, courseId);

            // 检查用户是否已购买课程
            UserCourse userCourse = userCourseMapper.findByUserIdAndCourseId(userId, courseId);
            if (userCourse == null || userCourse.getStatus() != 1) {
                return false;
            }

            // 检查是否已经在学习中
            if (redisTemplate.hasKey(learningKey)) {
                return true;
            }

            // 记录开始学习的时间
            Map<String, Object> learningData = new HashMap<>();
            learningData.put("startTime", LocalDateTime.now().toString());
            learningData.put("courseId", courseId);
            learningData.put("userId", userId);
            learningData.put("lastUpdate", LocalDateTime.now().toString());

            redisTemplate.opsForHash().putAll(learningKey, learningData);
            redisTemplate.expire(learningKey, PROGRESS_CACHE_HOURS, TimeUnit.HOURS);

            return true;
        } catch (Exception e) {
            throw new RuntimeException("开始学习失败", e);
        }
    }

    /**
     * 更新学习进度
     * @param userId 用户ID
     * @param courseId 课程ID
     * @param progress 学习进度百分比(0-100)
     * @return 是否成功更新进度的布尔值
     * @throws RuntimeException 当更新学习进度失败时抛出异常
     */
    @Override
    public boolean updateProgress(Integer userId, Integer courseId, Double progress) {
        try {
            if (progress < 0 || progress > 100) {
                return false;
            }

            String learningKey = RedisKeyUtil.getUserLearningKey(userId, courseId);
            String progressKey = RedisKeyUtil.getUserCourseProgressListKey(userId);

            // 更新Redis中的进度
            redisTemplate.opsForHash().put(learningKey, "progress", progress);
            redisTemplate.opsForHash().put(learningKey, "lastUpdate", LocalDateTime.now().toString());
            redisTemplate.expire(learningKey, PROGRESS_CACHE_HOURS, TimeUnit.HOURS);

            // 更新有序集合中的进度
            redisTemplate.opsForZSet().add(progressKey, courseId.toString(), progress);
            redisTemplate.expire(progressKey, PROGRESS_CACHE_HOURS, TimeUnit.HOURS);

            // 如果进度达到100%，标记为完成
            if (progress >= 100) {
                markCourseCompleted(userId, courseId);
            }

            return true;
        } catch (Exception e) {
            throw new RuntimeException("更新学习进度失败", e);
        }
    }

    /**
     * 记录学习时长
     * @param userId 用户ID
     * @param courseId 课程ID
     * @param minutes 学习时长（分钟）
     * @return 是否成功记录学习时长的布尔值
     * @throws RuntimeException 当记录学习时长失败时抛出异常
     */
    @Override
    public boolean recordStudyTime(Integer userId, Integer courseId, Integer minutes) {
        try {
            if (minutes <= 0) {
                return false;
            }

            String studyTimeKey = RedisKeyUtil.getUserTotalStudyTimeKey(userId);
            String courseStudyTimeKey = RedisKeyUtil.getCourseStudyTimeKey(courseId);
            String totalStudyTimeKey = RedisKeyUtil.getTotalStudyTimeKey();

            // 记录用户学习时长
            redisTemplate.opsForValue().increment(studyTimeKey, minutes);
            redisTemplate.expire(studyTimeKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);

            // 记录课程学习时长
            redisTemplate.opsForValue().increment(courseStudyTimeKey, minutes);
            redisTemplate.expire(courseStudyTimeKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);

            // 更新总学习时长排行榜
            redisTemplate.opsForZSet().incrementScore(totalStudyTimeKey, userId.toString(), minutes);
            redisTemplate.expire(totalStudyTimeKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);

            // 更新用户学习时长排行榜
            String userStudyTimeRankKey = RedisKeyUtil.getUserStudyTimeRankKey();
            redisTemplate.opsForZSet().incrementScore(userStudyTimeRankKey, userId.toString(), minutes);
            redisTemplate.expire(userStudyTimeRankKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);

            // 更新用户总学习时长（用于积分计算）
            updateUserTotalStudyTime(userId, minutes);

            return true;
        } catch (Exception e) {
            throw new RuntimeException("记录学习时长失败", e);
        }
    }

    /**
     * 获取学习进度
     * @param userId 用户ID
     * @param courseId 课程ID
     * @return 学习进度百分比(0-100)
     * @throws RuntimeException 当获取学习进度失败时抛出异常
     */
    @Override
    public Double getLearningProgress(Integer userId, Integer courseId) {
        try {
            String progressKey = RedisKeyUtil.getUserCourseProgressKey(userId,courseId);
            Double progress = redisTemplate.opsForZSet().score(progressKey, courseId.toString());

            if (progress == null) {
                // 从数据库获取进度
                UserCourse userCourse = userCourseMapper.findByUserIdAndCourseId(userId, courseId);
                if (userCourse != null && userCourse.getProgress() != null) {
                    progress = userCourse.getProgress();
                    // 缓存到Redis
                    redisTemplate.opsForZSet().add(progressKey, courseId.toString(), progress);
                    redisTemplate.expire(progressKey, PROGRESS_CACHE_HOURS, TimeUnit.HOURS);
                } else {
                    progress = 0.0;
                }
            }

            return progress;
        } catch (Exception e) {
            throw new RuntimeException("获取学习进度失败", e);
        }
    }

    /**
     * 获取学习时长
     * @param userId 用户ID
     * @param courseId 课程ID（可为null，表示获取总学习时长）
     * @return 学习时长（分钟）
     * @throws RuntimeException 当获取学习时长失败时抛出异常
     */
    @Override
    public Integer getStudyTime(Integer userId, Integer courseId) {
        try {
            String studyTimeKey = RedisKeyUtil.getUserTotalStudyTimeKey(userId);
            Object studyTime = redisTemplate.opsForValue().get(studyTimeKey);

            if (studyTime == null) {
                // 从数据库获取总学习时长
                Integer totalMinutes = userCourseMapper.getTotalStudyTimeByUserId(userId);
                if (totalMinutes != null) {
                    redisTemplate.opsForValue().set(studyTimeKey, totalMinutes, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);
                    return totalMinutes;
                }
                return 0;
            }

            return Integer.parseInt(studyTime.toString());
        } catch (Exception e) {
            throw new RuntimeException("获取学习时长失败", e);
        }
    }

    /**
     * 获取用户的学习进度列表
     * @param userId 用户ID
     * @return 学习进度列表，包含课程信息、进度百分比、学习时长等
     * @throws RuntimeException 当获取学习进度列表失败时抛出异常
     */
    @Override
    public List<Map<String, Object>> getLearningProgressList(Integer userId) {
        try {
            String progressKey = RedisKeyUtil.getUserCourseProgressListKey(userId);
            Set<ZSetOperations.TypedTuple<Object>> progressSet =
                    redisTemplate.opsForZSet().reverseRangeWithScores(progressKey, 0, -1);

            List<Map<String, Object>> progressList = new ArrayList<>();

            if (progressSet != null && !progressSet.isEmpty()) {
                for (ZSetOperations.TypedTuple<Object> tuple : progressSet) {
                    Integer courseId = Integer.valueOf((String) tuple.getValue());
                    Double progress = tuple.getScore();

                    Course course = courseMapper.selectById(courseId);
                    if (course != null) {
                        Map<String, Object> item = new HashMap<>();
                        item.put("courseId", courseId);
                        item.put("courseTitle", course.getTitle());
                        item.put("progress", progress);
                        item.put("courseImage", course.getCoverImage());
                        item.put("instructor", course.getInstructor());

                        // 获取学习时长
                        Integer studyTime = getStudyTime(userId, courseId);
                        item.put("studyTime", studyTime);

                        progressList.add(item);
                    }
                }
            } else {
                // 从数据库获取学习进度
                List<UserCourse> userCourses = userCourseMapper.findByUserId(userId);
                for (UserCourse userCourse : userCourses) {
                    if (userCourse.getProgress() != null && userCourse.getProgress() > 0) {
                        Course course = courseMapper.selectById(userCourse.getCourseId());
                        if (course != null) {
                            Map<String, Object> item = new HashMap<>();
                            item.put("courseId", userCourse.getCourseId());
                            item.put("courseTitle", course.getTitle());
                            item.put("progress", userCourse.getProgress());
                            item.put("courseImage", course.getCoverImage());
                            item.put("instructor", course.getInstructor());
                            item.put("studyTime", userCourse.getStudyTime() != null ? userCourse.getStudyTime() : 0);

                            progressList.add(item);
                        }
                    }
                }
            }

            return progressList;
        } catch (Exception e) {
            throw new RuntimeException("获取学习进度列表失败", e);
        }
    }

    /**
     * 获取学习时长排行榜
     * @param limit 排行榜限制数量
     * @return 学习时长排行榜列表，包含用户ID、学习时长、排名等信息
     * @throws RuntimeException 当获取学习时长排行榜失败时抛出异常
     */
    @Override
    public List<Map<String, Object>> getStudyTimeRanking(int limit) {
        try {
            String totalStudyTimeKey = RedisKeyUtil.getTotalStudyTimeKey();
            Set<ZSetOperations.TypedTuple<Object>> rankingSet =
                    redisTemplate.opsForZSet().reverseRangeWithScores(totalStudyTimeKey, 0, limit - 1);

            List<Map<String, Object>> rankingList = new ArrayList<>();

            if (rankingSet != null && !rankingSet.isEmpty()) {
                int rank = 1;
                for (ZSetOperations.TypedTuple<Object> tuple : rankingSet) {
                    Map<String, Object> item = new HashMap<>();
                    item.put("userId", Integer.valueOf((String) tuple.getValue()));
                    item.put("studyTime", tuple.getScore());
                    item.put("rank", rank++);
                    rankingList.add(item);
                }
            }

            return rankingList;
        } catch (Exception e) {
            throw new RuntimeException("获取学习时长排行榜失败", e);
        }
    }

    /**
     * 标记课程为已完成状态
     * @param userId 用户ID
     * @param courseId 课程ID
     * @return 是否成功标记课程完成的布尔值
     * @throws RuntimeException 当标记课程完成失败时抛出异常
     */
    @Override
    public boolean markCourseCompleted(Integer userId, Integer courseId) {
        try {
            String completedKey = RedisKeyUtil.getUserCompletedCoursesKey(userId);
            String progressKey = RedisKeyUtil.getUserCourseProgressKey(userId,courseId);

            // 添加到已完成课程集合
            redisTemplate.opsForSet().add(completedKey, courseId.toString());
            redisTemplate.expire(completedKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);

            // 更新数据库
            UserCourse userCourse = userCourseMapper.findByUserIdAndCourseId(userId, courseId);
            if (userCourse != null) {
                userCourse.setProgress(100.0);
                userCourse.setCompletedAt(LocalDateTime.now());
                userCourse.setStatus(2); // 已完成
                userCourseMapper.updateProgress(userCourse);
            }

            // 更新进度为100%
            redisTemplate.opsForZSet().add(progressKey, courseId.toString(), 100.0);

            return true;
        } catch (Exception e) {
            throw new RuntimeException("标记课程完成失败", e);
        }
    }

    /**
     * 获取用户已完成的课程列表
     * @param userId 用户ID
     * @return 已完成的课程ID集合
     * @throws RuntimeException 当获取已完成课程失败时抛出异常
     */
    @Override
    public Set<Integer> getCompletedCourses(Integer userId) {
        try {
            String completedKey = RedisKeyUtil.getUserCompletedCoursesKey(userId);
            Set<Object> completedSet = redisTemplate.opsForSet().members(completedKey);

            Set<Integer> completedCourses = new HashSet<>();
            if (completedSet != null && !completedSet.isEmpty()) {
                for (Object courseIdStr : completedSet) {
                    completedCourses.add(Integer.valueOf((String) courseIdStr));
                }
            } else {
                // 从数据库获取已完成的课程
                List<UserCourse> completedUserCourses = userCourseMapper.findCompletedByUserId(userId);
                for (UserCourse userCourse : completedUserCourses) {
                    completedCourses.add(userCourse.getCourseId());
                    redisTemplate.opsForSet().add(completedKey, userCourse.getCourseId().toString());
                }
                redisTemplate.expire(completedKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);
            }

            return completedCourses;
        } catch (Exception e) {
            throw new RuntimeException("获取已完成课程失败", e);
        }
    }

    /**
     * 获取用户的学习统计信息
     * @param userId 用户ID
     * @return 学习统计信息，包含总学习时长、已完成课程数量、进行中课程数量、平均学习进度、学习时长排名等
     * @throws RuntimeException 当获取学习统计失败时抛出异常
     */
    @Override
    public Map<String, Object> getLearningStats(Integer userId) {
        try {
            Map<String, Object> stats = new HashMap<>();

            // 总学习时长
            Integer totalStudyTime = getStudyTime(userId, null);
            stats.put("totalStudyTime", totalStudyTime);

            // 已完成课程数量
            Set<Integer> completedCourses = getCompletedCourses(userId);
            stats.put("completedCourses", completedCourses.size());

            // 进行中课程数量
            List<Map<String, Object>> progressList = getLearningProgressList(userId);
            Integer inProgressCourses = (int) progressList.stream()
                    .filter(item -> (Double) item.get("progress") < 100.0)
                    .count();
            stats.put("inProgressCourses", inProgressCourses);

            // 平均学习进度
            double avgProgress = progressList.stream()
                    .mapToDouble(item -> (Double) item.get("progress"))
                    .average()
                    .orElse(0.0);
            stats.put("averageProgress", avgProgress);

            // 学习时长排名
            String totalStudyTimeKey = RedisKeyUtil.getTotalStudyTimeKey();
            Long rank = redisTemplate.opsForZSet().reverseRank(totalStudyTimeKey, userId.toString());
            stats.put("studyTimeRank", rank != null ? rank + 1 : null);

            return stats;
        } catch (Exception e) {
            throw new RuntimeException("获取学习统计失败", e);
        }
    }

    /**
     * 将学习进度保存到数据库
     * @param userId 用户ID
     * @param courseId 课程ID
     * @return 是否成功保存到数据库的布尔值
     * @throws RuntimeException 当保存进度到数据库失败时抛出异常
     */
    @Override
    public boolean saveProgressToDatabase(Integer userId, Integer courseId) {
        try {
            Double progress = getLearningProgress(userId, courseId);
            Integer studyTime = getStudyTime(userId, courseId);

            UserCourse userCourse = userCourseMapper.findByUserIdAndCourseId(userId, courseId);
            if (userCourse != null) {
                userCourse.setProgress(progress);
                userCourse.setStudyTime(studyTime);
                userCourse.setUpdatedAt(LocalDateTime.now());

                if (progress >= 100) {
                    userCourse.setStatus(2); // 已完成
                    userCourse.setCompletedAt(LocalDateTime.now());
                } else if (progress > 0) {
                    userCourse.setStatus(1); // 学习中
                }

                userCourseMapper.updateProgress(userCourse);
                return true;
            }

            return false;
        } catch (Exception e) {
            throw new RuntimeException("保存进度到数据库失败", e);
        }
    }

    /**
     * 更新用户总学习时长
     * @param userId 用户ID
     * @param minutes 新增的学习时长（分钟）
     */
    private void updateUserTotalStudyTime(Integer userId, Integer minutes) {
        try {
            String userTotalStudyTimeKey = RedisKeyUtil.getUserTotalStudyTimeKey(userId);
            redisTemplate.opsForValue().increment(userTotalStudyTimeKey, minutes);
            redisTemplate.expire(userTotalStudyTimeKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);
        } catch (Exception e) {
            throw new RuntimeException("更新用户总学习时长失败", e);
        }
    }

    /**
     * 更新用户学习时长排行榜
     * @param userId 用户ID
     * @param minutes 新增的学习时长（分钟）
     */
    private void updateStudyTimeRanking(Integer userId, Integer minutes) {
        try {
            String totalStudyTimeKey = RedisKeyUtil.getTotalStudyTimeKey();
            
            // 获取当前总学习时长
            String currentTotalKey = RedisKeyUtil.getStudyTimeKey(userId, null);
            String currentTotalStr = (String) redisTemplate.opsForValue().get(currentTotalKey);
            Integer currentTotal = currentTotalStr != null ? Integer.valueOf(currentTotalStr) : 0;
            
            // 更新排行榜
            redisTemplate.opsForZSet().add(totalStudyTimeKey, userId.toString(), currentTotal);
            redisTemplate.expire(totalStudyTimeKey, STUDY_TIME_RANK_DAYS, TimeUnit.DAYS);
        } catch (Exception e) {
            throw new RuntimeException("更新学习时长排行榜失败", e);
        }
    }

    /**
     * 缓存学习时长到Redis
     * @param userId 用户ID
     * @param courseId 课程ID
     * @param minutes 学习时长（分钟）
     */
    private void cacheStudyTime(Integer userId, Integer courseId, Integer minutes) {
        try {
            String studyTimeKey = RedisKeyUtil.getStudyTimeKey(userId, courseId);
            redisTemplate.opsForValue().set(studyTimeKey, minutes.toString(), PROGRESS_CACHE_HOURS, TimeUnit.HOURS);
        } catch (Exception e) {
            throw new RuntimeException("缓存学习时长失败", e);
        }
    }
}